<template>
	<view>
		<ai-header :title="modelName" @confirm="confirmResources"></ai-header>
		<view class="container">
			<view class="history">
				<view class="new-chat" @click="newChat(0)">
					<image class="icon" mode="aspectFill" src="../../static/ai/new-chat.png"></image>
					<span>开启新对话</span>
				</view>
				<scroll-view scroll-y class="history-list" @scrolltolower="loadHistory">
					<view v-for="(group, k) in history" :key="k" class="group">
						<view class="title">{{ group.group }}</view>
						<view v-for="(item, kk) in group.list" :key="kk" class="list">
							<view :class="{item: true, 'u-line-1': true, active: item.id == completionId}"
								@click="newChat(item.id)">{{ item.title }}</view>
						</view>
					</view>
					<u-loadmore v-if="historyStatus == 1" status="loading" loadingText="" />
				</scroll-view>
			</view>
			<view class="chat-box" :style="chating ? 'justify-content: space-between;' : ''">
				<view v-if="!chating">
					<view class="title">
						<image class="logo" mode="aspectFill" src="../../static/ai/kimi.png"></image>
						KIMI
					</view>
				</view>
				<scroll-view scroll-y v-else class="chat" :scroll-top="chatContainerScrollTop"
					@scroll="chatContainerScroll" @scrolltoupper="loadConversationHistory">
					<u-loadmore v-if="conversationStatus == 1" status="loading" loadingText="" />
					<template v-for="(chat, key) in conversation">
						<view v-if="chat.type == 'question'" class="question">
							<view v-if="chat.img.length" class="img-list">
								<image v-for="(img, i) in chat.img" :key="i" class="img" mode="aspectFill" :src="img"></image>
							</view>
							<view class="text">{{ chat.content }}</view>
						</view>
						<view v-if="chat.type == 'answer'" class="answer">
							<image class="avatar" mode="aspectFill" src="../../static/ai/kimi-avatar.png"></image>
							<view class="content">
								<u-loading-icon v-if="answerWaiting && key === conversation.length - 1" mode="circle"
									timingFunction="linear"
									style="height: 33rpx; justify-content: left;"></u-loading-icon>
								<view v-else-if="chat.error" class="text error">{{ chat.error }}</view>
								<template v-else>
									<view class="text" v-html="chat.content"></view>
									<view v-show="!answering || key !== conversation.length - 1" class="extend-box">
										<share :id="chat.id"></share>
										<view class="extend" @click="again(key)">
											<image class="icon" mode="aspectFill" src="../../static/ai/refresh.png">
											</image>
											再来一次
										</view>
									</view>
								</template>
							</view>
						</view>
					</template>
				</scroll-view>
				<view :class="{'input-box': true, 'not-chating': !chating, 'has-img': !!inputImg.length}">
					<view v-if="inputImg.length" class="img-list">
						<view v-for="(img, i) in inputImg" :key="i" class="img-box">
							<u-icon class="close" name="close-circle" @click="delInputImg(i)"></u-icon>
							<image class="img" mode="aspectFill" :src="img"></image>
						</view>
					</view>
					<textarea v-model="input" placeholder="随时@你想要的Kimi+ 使用各种能力" class="input" maxlength="2048"
						auto-height　rows="2"></textarea>
					<view class="tool">
						<view class="block">
							<view :class="{btn: true, active: webSearch}" @click="webSearch = !webSearch">
								<image class="icon" mode="aspectFill"
									:src="'../../static/ai/intnet'+(webSearch ? '-active' : '')+'.png'"></image>
								联网搜索
							</view>
						</view>
						<view class="block">
							<speech @recognize="voice"></speech>
							<view :class="{sumbit: true, active: (!!input || answering)}" @click="question">
								<u-loading-icon v-if="answerWaiting" timingFunction="linear" mode="semicircle"
									class="text" color="#FFFFFF" size="28"></u-loading-icon>
								<u-icon v-else :name="answering ? 'close' : 'arrow-upward'" color="#FFFFFF"
									size="24"></u-icon>
							</view>
						</view>
					</view>
				</view>
			</view>
		</view>
		<view class="footer">内容由AI生成，无法确保真实准确，仅供参考</view>
		<xe-event-source ref="eventSource" :url="eventSourceUrl" :options="eventSourceOptions"
			@callback="eventSourceCallback"></xe-event-source>
	</view>
</template>
<script>
	import aiHeader from './components/header'
	import share from './components/share'
	import speech from './components/speech'
	import {
		HTTP_REQUEST_URL,
		HEADER,
		TOKENNAME,
		HEADERPARAMS
	} from '@/config/app'
	import kimiApi from '@/api/kimi.js'
	import MarkdownIt from 'markdown-it'

	export default {
		data() {
			return {
				model: null,
				modelName: '',
				input: '',
				inputImg: [],
				inputSend: '',
				inputImgSend: [],
				completionId: 0,
				chating: false,
				currentAnswer: null,
				answering: false,
				answerWaiting: false,
				outputInterval: null,
				answerContentArr: [],
				answerContent: '',
				canClearInterval: false,
				deepThink: false,
				webSearch: false,
				history: [],
				historyPage: 1,
				historyStatus: 0,
				conversation: [],
				conversationLastId: 0,
				conversationStatus: 0,
				oldChatContainerScrollTop: 0,
				chatContainerScrollTop: 0
			}
		},
		computed: {
			eventSourceUrl() {
				return HTTP_REQUEST_URL + '/api/ai.kimi/completions'
			},
			eventSourceOptions({
				inputContent
			}) {
				return {
					headers: {
						'Content-Type': 'application/json',
					},
					method: 'POST',
					body: JSON.stringify({
						id: this.completionId,
						prompt: this.inputSend,
						img: this.inputImgSend,
						deepThink: this.deepThink ? 1 : 0,
						webSearch: this.webSearch ? 1 : 0,
						model: this.model
					})
				}
			}
		},
		components: {
			aiHeader,
			share,
			speech
		},
		onLoad(option) {
			if (typeof option.model != 'undefined') {
				this.model = option.model
				if (option.name) {
					uni.setStorageSync('ai-model-name', option.name)
					this.modelName = option.name
				} else {
					this.modelName = uni.getStorageSync('ai-model-name')
				}
			}
			if (typeof option.id != 'undefined') {
				this.newChat(option.id)
			}
			this.getHistoryList()
		},
		methods: {
			async newChat(id) {
				this.input = ''
				this.chating = false
				this.answering = false
				this.answerWaiting = false
				this.deepThink = false
				this.webSearch = false
				this.conversation = []
				this.conversationStatus = 0
				this.conversationLastId = 0
				if (this.$refs.eventSource) {
					this.$refs.eventSource.close()
				}
				if (id && id != this.completionId) {
					this.completionId = id
					await this.loadConversationHistory()
					this.chating = true
				}
				if (!id) {
					this.completionId = 0
					this.conversationStatus = 2
				}
			},
			confirmResources(e) {
				if (e.type == 'image') {
					this.inputImg = e.content
				} else {
					this.input = e.content
				}
			},
			delInputImg(i) {
				this.inputImg.splice(i, 1)
			},
			voice(e) {
				this.input += e
			},
			async question() {
				if (this.answering) {
					if (this.answerWaiting) {
						return
					}
					if (this.$refs.eventSource) {
						this.$refs.eventSource.close()
						this.answering = false
						this.answerWaiting = false
					}
					return
				}
				const input = this.input
				const inputImg = this.inputImg
				this.input = ''
				this.inputImg = []
				this.chating = true
				this.answering = true
				this.answerWaiting = true
				this.currentAnswer = {
					type: 'answer',
					id: '',
					error: '',
					content: ''
				}
				this.conversation.push({
					type: 'question',
					content: input,
					img: inputImg
				}, this.currentAnswer)
				this.autoScrollChat()
				if (!this.completionId) {
					const response = await kimiApi.create({
						'model': this.model,
						'prompt': input
					})
					this.completionId = response.data.data

					if (!this.history.length || this.history[0].group != '今天') {
						this.history.unshift({
							group: '今天',
							list: []
						})
					}
					this.history[0].list.unshift({
						id: this.completionId,
						title: input
					})
				}
				this.canClearInterval = false
				this.outputInterval = null
				this.answerContent = ''
				this.answerContentArr = []
				this.inputSend = input
				this.inputImgSend = inputImg
				this.$refs.eventSource.send()
			},
			eventSourceCallback(e) {
				const {
					type,
					msg,
					data
				} = e || {}
				switch (type) {
					case 'onopen':
						this.answerWaiting = false
						this.outputInterval = setInterval(() => {
							const char = this.answerContentArr.shift()
							if (char) {
								this.answerContent += char
								const md = new MarkdownIt()
								this.currentAnswer.content = md.render(this.answerContent)
								this.autoScrollChat()
							} else if (this.canClearInterval) {
								this.conversation[this.conversation.length - 1] = JSON.parse(JSON.stringify(this
									.currentAnswer))
								clearInterval(this.outputInterval)
								setTimeout(this.autoScrollChat, 50)
							}
						}, 10)
						break
					case 'onmessage':
						if (data && data != '[DONE]') {
							const res = JSON.parse(data)
							if (!this.currentAnswer.id) {
								this.currentAnswer.id = res.id
							}
							for (let i in res.choices) {
								this.answerContentArr.push(res.choices[i].delta.content)
							}
						}
						break
					case 'onclose':
						this.canClearInterval = true
						this.answering = false
						break
					case 'onerror':
						this.currentAnswer.error = data
						this.answerWaiting = false
						this.answering = false
						this.conversation[this.conversation.length - 1] = JSON.parse(JSON.stringify(this.currentAnswer))
						this.autoScrollChat()
						return null
						break
				}
			},
			copy(index) {
				uni.setClipboardData({
					data: this.conversation[index].content.replace(/<[^>]*>/g, ''),
					success: () => {
						uni.showToast({
							title: '内容已复制',
							icon: 'none'
						})
					}
				})
			},
			again(index) {
				this.input = this.conversation[index - 1].content
				this.question()
			},
			autoScrollChat() {
				this.chatContainerScrollTop = this.oldChatContainerScrollTop
				this.$nextTick(() => {
					this.chatContainerScrollTop = this.oldChatContainerScrollTop + 9999
				})
			},
			chatContainerScroll(e) {
				this.oldChatContainerScrollTop = e.detail.scrollTop
			},
			loadHistory() {
				if (this.historyStatus != 0) {
					return
				}
				this.historyPage += 1
				this.getHistoryList()
			},
			getHistoryList() {
				const pageSize = 20
				this.historyStatus = 1
				kimiApi.history({
					model: this.model,
					page: this.historyPage,
					pageSize: pageSize
				}).then(res => {
					for (const item of res.data.data) {
						let index
						const group = this.history.find(v => {
							return v.group == item.group
						})
						if (group) {
							index = this.history.indexOf(group)
						} else {
							this.history.push({
								group: item.group,
								list: []
							})
							index = this.history.length - 1
						}
						this.history[index].list.push(item)
					}
					this.historyStatus = 0
					if (res.data.data.length < pageSize) {
						this.historyStatus = 2
					}
				})
			},
			loadConversationHistory() {
				if (this.conversationStatus != 0) {
					return
				}
				this.conversationStatus = 1
				const pageSize = 20
				kimiApi.conversationHistory({
					chatId: this.completionId,
					lastId: this.conversationLastId,
					pageSize: pageSize
				}).then(res => {
					for (const item of res.data.data) {
						const md = new MarkdownIt()
						const val = {
							id: item.completion_id,
							img: []
						}
						if (item.role == 'user') {
							val.type = 'question'
							val.content = item.content
							if (typeof item.img != 'undefined') {
								val.img = item.img
							}
						} else {
							val.type = 'answer'
							val.error = ''
							val.content = md.render(item.content)
						}
						this.conversation.unshift(val)
						this.conversationLastId = item.id
					}
					this.conversationStatus = 0
					if (res.data.data.length < pageSize) {
						this.conversationStatus = 2
					}
					this.autoScrollChat()
				})
			}
		}
	}
</script>

<style lang="scss">
	.input-placeholder {
		font-size: 20rpx;
		color: #404040;
	}

	.container {
		display: flex;
		height: calc(100vh - 80rpx);

		.history {
			width: 300rpx;

			.new-chat {
				margin: 20rpx 48rpx;
				background-color: #DBEAFE;
				border-radius: 8rpx;
				color: #4D6BFE;
				padding: 7rpx 0;
				width: calc(100% - 96rpx);
				display: flex;
				justify-content: center;
				align-items: center;
				column-gap: 8rpx;
				font-size: 20rpx;

				.icon {
					width: 32rpx;
					height: 32rpx;
				}
			}

			.history-list {
				height: calc(100% - 130rpx);
				overflow-y: auto;
			}

			.group {
				margin: 20rpx 12rpx 10rpx 18rpx;

				.title {
					font-weight: 600;
					margin: 10rpx 0 10rpx 12rpx;
					color: #333333;
					font-size: 20rpx;
				}

				.list {
					margin-top: 10rpx;
					color: #666666;
					font-size: 18rpx;
					display: flex;
					flex-direction: column;
					row-gap: 12rpx;

					.item {
						padding: 4rpx 16rpx;

						&.active {
							border-radius: 16rpx;
							background-color: #DBEAFE;
						}
					}
				}
			}
		}

		.chat-box {
			width: calc(100% - 300rpx);
			height: calc(100% - 80rpx);
			display: flex;
			flex-direction: column;
			align-items: center;

			.title {
				font-weight: 600;
				margin-top: 155rpx;
				font-size: 86rpx;
				color: #333333;
				display: flex;
				justify-content: center;
				align-items: center;

				.logo {
					width: 96rpx;
					height: 96rpx;
					margin-right: 12rpx;
				}
			}

			.sub-title {
				margin-top: 34rpx;
				color: #666666;
				font-size: 24rpx;
			}

			.chat {
				width: calc(100% - 120rpx);
				height: 100%;
				margin: 48rpx 60rpx 12rpx 60rpx;
				font-size: 20rpx;
				overflow-y: auto;

				.question {
					display: flex;
					flex-direction: column;
					align-items: flex-end;
					
					.img-list {
						display: flex;
						column-gap: 8rpx;
						margin-bottom: 8rpx;
						
						.img {
							max-width: 220rpx;
							max-height: 220rpx;
							border: 1rpx solid #dedadf;
							border-radius: 8rpx;
						}
					}
					
					.text {
						background-color: #3577F5;
						color: #FFFFFF;
						border-radius: 4rpx;
						padding: 15rpx 30rpx;
						line-height: 33rpx;
					}
				}

				.answer {
					display: flex;
					margin-top: 42rpx;
					margin-bottom: 30rpx;

					.avatar {
						width: 44rpx;
						height: 44rpx;
						margin-right: 33rpx;
					}

					.content {
						width: calc(100% - 77rpx);
						display: flex;
						flex-direction: column;

						.text {
							color: #333333;
							line-height: 33rpx;

							&.error {
								color: #ff0000;
							}
						}

						.extend-box {
							display: flex;
							column-gap: 45rpx;
							color: #999999;
							font-size: 18rpx;
							margin-top: 22rpx;

							.extend {
								display: flex;

								.icon {
									width: 22rpx;
									height: 22rpx;
									margin-right: 8rpx;
								}
							}
						}
					}
				}
			}

			.input-box {
				margin-top: 18rpx;
				width: 940rpx;
				border-radius: 32rpx;
				background-color: #f3f4f6;
				padding: 12rpx 24rpx;
				display: flex;
				flex-direction: column;
				border: solid 1rpx #dedadf;
				justify-content: space-between;
				min-height: 112rpx;

				&.not-chating {
					margin-top: 55rpx;
				}
				
				&.has-img {
					min-height: 220rpx;
				}

				.img-list {
					display: flex;
					justify-content: flex-start;
					column-gap: 8rpx;
					padding: 8rpx 0;
					border-bottom: 1rpx solid #dedadf;
					
					.img-box {
						position: relative;
						
						.close {
							z-index: 999;
							position: absolute;
							right: 0;
						}
						
						.img {
							border: 1rpx solid #dedadf;
							border-radius: 8rpx;
							width: 64rpx;
							height: 64rpx;
						}
					}
				}

				.input {
					font-size: 24rpx;
					resize: none;
					color: #404040;
					caret-color: #404040;
					background-color: transparent;
					line-height: 32rpx;
					word-break: break-word;
					white-space: pre-wrap;
					border: none;
					width: 100%;
					margin: 0;
					padding: 0;
					display: block;
					top: 0;
					bottom: 0;
					left: 0;
					right: 0;
					overflow: auto;
					max-height: 180rpx;
				}

				.tool {
					margin-top: 18rpx;
					width: 100%;
					display: flex;
					justify-content: space-between;

					.block {
						display: flex;
						column-gap: 12rpx;
						align-items: center;
					}

					.attachment {
						width: 32rpx;
						height: 32rpx;
						margin-right: 8rpx;
					}

					.btn {
						font-size: 16rpx;
						color: #333333;
						border-radius: 24rpx;
						padding: 6rpx 18rpx;
						border: solid 1rpx #c1bec2;
						height: 24rpx;
						display: flex;
						align-items: center;

						&.active {
							color: #617EF6;
							border-color: #4D6BFE;
							background-color: #DEEAFC;
						}

						.icon {
							width: 20rpx;
							height: 20rpx;
							margin-right: 6rpx;
						}
					}

					.sumbit {
						font-size: 20rpx;
						display: flex;
						height: 48rpx;
						width: 48rpx;
						border-radius: 24rpx;
						background-color: #d7dee7;
						display: flex;
						justify-content: center;
						align-items: center;

						&.active {
							background-color: #4d6bfe
						}
					}
				}
			}
		}
	}

	.footer {
		position: absolute;
		bottom: 16rpx;
		width: 100%;
		font-size: 20rpx;
		color: #999999;
		text-align: center;
	}
</style>